using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

public class Keyframe
{
	public Keyframe(int bone, TimeSpan time, Matrix transform)
	{
		Bone = bone;
		Time = time;
		Transform = transform;
	}
	private Keyframe() { }
	[ContentSerializer]
	public int Bone { get; private set; }
	[ContentSerializer]
	public TimeSpan Time { get; private set; }
	[ContentSerializer]
	public Matrix Transform { get; private set; }
}

public class AnimationClip
{
	public AnimationClip(TimeSpan duration, List<Keyframe> keyframes)
	{
		Duration = duration;
		Keyframes = keyframes;
	}
	private AnimationClip() { }
	[ContentSerializer]
	public TimeSpan Duration { get; private set; }
	[ContentSerializer]
	public List<Keyframe> Keyframes { get; private set; }
}

public class SkinningData
{
	public SkinningData(Dictionary<string, AnimationClip> animationClips,
						List<Matrix> bindPose, List<Matrix> inverseBindPose,
						List<int> skeletonHierarchy)
	{
		AnimationClips = animationClips;
		BindPose = bindPose;
		InverseBindPose = inverseBindPose;
		SkeletonHierarchy = skeletonHierarchy;
	}
	private SkinningData() { }
	[ContentSerializer]
	public Dictionary<string, AnimationClip> AnimationClips
	{
		get;
		private set;
	}
	[ContentSerializer]
	public List<Matrix> BindPose { get; private set; }
	[ContentSerializer]
	public List<Matrix> InverseBindPose { get; private set; }
	[ContentSerializer]
	public List<int> SkeletonHierarchy { get; private set; }
}

public class AnimationPlayer
{
	AnimationClip currentClipValue;
	TimeSpan currentTimeValue;
	int currentKeyframe;
	Matrix[] boneTransforms;
	Matrix[] worldTransforms;
	Matrix[] skinTransforms;
	SkinningData skinningDataValue;
	public AnimationPlayer(SkinningData skinningData)
	{
		if (skinningData == null)
			throw new ArgumentNullException("skinningData");
		skinningDataValue = skinningData;
		boneTransforms = new Matrix[skinningData.BindPose.Count];
		worldTransforms = new Matrix[skinningData.BindPose.Count];
		skinTransforms = new Matrix[skinningData.BindPose.Count];
	}
	public void StartClip(AnimationClip clip)
	{
		if (clip == null)
			throw new ArgumentNullException("clip");
		currentClipValue = clip;
		currentTimeValue = TimeSpan.Zero;
		currentKeyframe = 0;
		// Initialize bone transforms to the bind pose.
		skinningDataValue.BindPose.CopyTo(boneTransforms, 0);
	}
	public void Update(TimeSpan time, bool relativeToCurrentTime,
					   Matrix rootTransform)
	{
		UpdateBoneTransforms(time, relativeToCurrentTime);
		UpdateWorldTransforms(rootTransform);
		UpdateSkinTransforms();
	}
	public void UpdateBoneTransforms(TimeSpan time,
		bool relativeToCurrentTime)
	{
		if (currentClipValue == null)
			throw new InvalidOperationException(
						"AnimationPlayer.Update was called before StartClip");
		// Update the animation position.
		if (relativeToCurrentTime)
		{
			time += currentTimeValue;
			// If we reached the end, loop back to the start.
			while (time >= currentClipValue.Duration)
				time -= currentClipValue.Duration;
		}
		if ((time < TimeSpan.Zero) || (time >= currentClipValue.Duration))
			throw new ArgumentOutOfRangeException("time");
		// If the position moved backwards, reset the keyframe index.
		if (time < currentTimeValue)
		{
			currentKeyframe = 0;
			skinningDataValue.BindPose.CopyTo(boneTransforms, 0);
		}
		currentTimeValue = time;
		// Read keyframe matrices.
		IList<Keyframe> keyframes = currentClipValue.Keyframes;
		while (currentKeyframe < keyframes.Count)
		{
			Keyframe keyframe = keyframes[currentKeyframe];
			// Stop when we've read up to the current time position.
			if (keyframe.Time > currentTimeValue)
				break;
			// Use this keyframe.
			boneTransforms[keyframe.Bone] = keyframe.Transform;
			currentKeyframe++;
		}
	}
	public void UpdateWorldTransforms(Matrix rootTransform)
	{
		// Root bone.
		worldTransforms[0] = boneTransforms[0] * rootTransform;
		// Child bones.
		for (int bone = 1; bone < worldTransforms.Length; bone++)
		{
			int parentBone = skinningDataValue.SkeletonHierarchy[bone];
			worldTransforms[bone] = boneTransforms[bone] *
										 worldTransforms[parentBone];
		}
	}
	public void UpdateSkinTransforms()
	{
		for (int bone = 0; bone < skinTransforms.Length; bone++)
		{
			skinTransforms[bone] = skinningDataValue.InverseBindPose[bone] *
										worldTransforms[bone];
		}
	}
	public Matrix[] GetBoneTransforms()
	{
		return boneTransforms;
	}
	public Matrix[] GetWorldTransforms()
	{
		return worldTransforms;
	}
	public Matrix[] GetSkinTransforms()
	{
		return skinTransforms;
	}
	public AnimationClip CurrentClip
	{
		get { return currentClipValue; }
	}
	public TimeSpan CurrentTime
	{
		get { return currentTimeValue; }
	}
}